﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using HtmlAgilityPack;

using SSTU.Contract.Common;
using SSTU.Contract.Facade;
using SSTU.Extensions;
using SSTU.Model;

namespace SSTU.Facade
{
	public class TimeTableFacade : FacadeBase, ITimeTableFacade
	{
		/// <summary>
		/// Loads the time table for group asynchronously and invoke callback.
		/// </summary>
		/// <param name="groupId">The group identifier.</param>
		/// <param name="asyncCallback">The async callback.</param>
		public void LoadTimeTableForGroupAsync(string groupId, FacadeAsyncCallback<GroupTimeTable> asyncCallback)
		{
			var syncContext = SynchronizationContext.Current;
			ThreadPool.QueueUserWorkItem(state =>
			{
				string requestUriPattern = @"http://rasp.sstu.ru/htmlViewRaspBook.aspx?GROUP={0}&OLD=00";
				string requestUriString = string.Format(requestUriPattern, groupId);
				bool isCallbackInvoked = false;
				HtmlWeb.LoadAsync(requestUriString, (o, e) =>
				{
					// HACK: Protect from the HtmlAgilityPack bug with double callback invocation.
					if (!isCallbackInvoked)
					{
						isCallbackInvoked = true;
						var response = Do<GroupTimeTable>(FacadeResponseDataSource.Server, () =>
						{
							var groupTimeTable = new GroupTimeTable();
							groupTimeTable.Group = new Group(groupId, null);
							// Первая всегда идет нечетная неделя, второй - четная.
							var htmlWeeks = e.Document.DocumentNode.SelectNodes("//table[@align='center' and @border=0 and @cellpadding=2]/tbody");
							if (htmlWeeks != null)
							{
								var oddWeek = ParseHtmlWeek(htmlWeeks[0], PeriodicWeekType.Odd);
								groupTimeTable.OddWeek = oddWeek;
								var evenWeek = ParseHtmlWeek(htmlWeeks[1], PeriodicWeekType.Even);
								groupTimeTable.EvenWeek = evenWeek;
							}
							return groupTimeTable;
						});
						syncContext.Post(@null => asyncCallback(response), null);
					}
				});
			});
		}

		/// <summary>
		/// Parses the time table week from HTML node.
		/// </summary>
		/// <param name="htmlNode">The HTML node.</param>
		/// <param name="weekType">Type of the week.</param>
		private PeriodicWeek ParseHtmlWeek(HtmlNode htmlNode, PeriodicWeekType weekType)
		{
			var week = new PeriodicWeek() { Type = weekType };
			week.Days = new List<PeriodicWeekDay>()
			{
				new PeriodicWeekDay(DayOfWeek.Monday, true),
				new PeriodicWeekDay(DayOfWeek.Tuesday, true),
				new PeriodicWeekDay(DayOfWeek.Wednesday, true),
				new PeriodicWeekDay(DayOfWeek.Thursday, true),
				new PeriodicWeekDay(DayOfWeek.Friday, true),
				new PeriodicWeekDay(DayOfWeek.Saturday, true),
				new PeriodicWeekDay(DayOfWeek.Sunday, true),
			};
			// Первые две строки это название недели и дни.
			// First two rows it's the headers.
			foreach (var htmlTr in htmlNode.Elements("tr").Skip(2))
			{
				var htmlBWithTime = htmlTr.ChildNodes[1].SelectSingleNode("div/font/b");
				var htmlBSplittedString = htmlBWithTime.InnerHtml.Split(new string[] { "<br>" }, StringSplitOptions.RemoveEmptyEntries);
				string startTimeString = htmlBSplittedString[0].Trim().Replace(".", ":");
				TimeSpan startTime = TimeSpan.Parse(startTimeString);
				string endTimeString = htmlBSplittedString[1].Trim().Replace(".", ":");
				TimeSpan endTime = TimeSpan.Parse(endTimeString);
				for (int htmlTdIndex = 2; htmlTdIndex < htmlTr.ChildNodes.Count; htmlTdIndex++)
				{
					var htmlTd = htmlTr.ChildNodes[htmlTdIndex];
					var htmlRoomA = htmlTd.SelectSingleNode("div[@align='center']/a[@href]");
					if (htmlRoomA != null)
					{
						var newClass = new Class();
						newClass.Start = startTime;
						newClass.End = endTime;
						// Standart format for class room number it is:
						// "<BuildingId>/<SubRoomId>/<RoomId>"
						// Exists special format for organizations and classes not in the university:
						// "<RoomId>/<OrganizationName>"
						// For special rooms, which can exist only in one known building, exist next format:
						// "/<SubRoomId>/<RoomName>"
						string[] classRoomAndBuildingParts = htmlRoomA.FirstChild.InnerText.Split('/');
						if (classRoomAndBuildingParts.Length == 1)
						{
							newClass.RoomId = classRoomAndBuildingParts[0];
						}
						else if (classRoomAndBuildingParts.Length == 2)
						{
							newClass.RoomId = classRoomAndBuildingParts[0];
							newClass.BuildingOrOrganizationId = classRoomAndBuildingParts[1];
						}
						else if (classRoomAndBuildingParts.Length == 3)
						{
							newClass.RoomId = classRoomAndBuildingParts[2];
							newClass.BuildingOrOrganizationId = classRoomAndBuildingParts[0];
							newClass.SubRoomId = classRoomAndBuildingParts[1];
						}
						var htmlThemeFont = htmlTd.SelectSingleNode("div[@align='center']/font[@size=2][1]");
						newClass.Theme = htmlThemeFont.InnerText.NormalizeWhitespace();
						var htmlLectorerA = htmlTd.SelectSingleNode("div[@align='center']/font[@size=2][2]/a");
						newClass.Lecturer = new User() { FullName = htmlLectorerA.InnerText };
						newClass.Id = ((int)weekType).ToString() + (htmlTdIndex - 2).ToString()  + newClass.Start.ToString() + newClass.End.ToString() + newClass.RoomId + newClass.BuildingOrOrganizationId + newClass.SubRoomId;
						week.Days[htmlTdIndex - 2].Classes.Add(newClass);
					}
				}
			}
			return week;
		}
	}
}
